home *** CD-ROM | disk | FTP | other *** search
/ Clickx 115 / Clickx 115.iso / software / tools / windows / tails-i386-0.16.iso / live / filesystem.squashfs / usr / share / pyshared / whisperBack / gui.py < prev    next >
Encoding:
Python Source  |  2012-12-13  |  16.2 KB  |  437 lines

  1. #!/usr/bin/env python
  2. # -*- coding: UTF-8 -*-
  3.  
  4. """WhisperBack GUI
  5.  
  6. """
  7.  
  8. ########################################################################
  9. __licence__ = """
  10. WhisperBack - Send feedback in an encrypted mail
  11. Copyright (C) 2009-2012 Tails developers <tails@boum.org>
  12.  
  13. This file is part of WhisperBack
  14.  
  15. WhisperBack is  free software; you can redistribute  it and/or modify
  16. it under the  terms of the GNU General Public  License as published by
  17. the Free Software Foundation; either  version 3 of the License, or (at
  18. your option) any later version.
  19.  
  20. This program  is distributed in the  hope that it will  be useful, but
  21. WITHOUT   ANY  WARRANTY;   without  even   the  implied   warranty  of
  22. MERCHANTABILITY  or FITNESS  FOR A  PARTICULAR PURPOSE.   See  the GNU
  23. General Public License for more details.
  24.  
  25. You should have received a copy of the GNU General Public License
  26. along with this program.  If not, see <http://www.gnu.org/licenses/>.
  27. """
  28. ########################################################################
  29.  
  30. __version__ = '1.6.4'
  31. LOCALEDIR = "locale/"
  32. PACKAGE = "whisperback"
  33.  
  34. import os
  35.  
  36. import pygtk
  37. pygtk.require('2.0')
  38. import gtk
  39. import gobject
  40.  
  41. import webkit
  42. import webbrowser
  43.  
  44. # Used by show_exception_dialog
  45. import traceback
  46. import types
  47.  
  48. # Import these because we need the exception they raise
  49. import smtplib
  50. import socket
  51.  
  52. # Import our modules
  53. import whisperBack.exceptions
  54. import whisperBack.whisperback
  55. import whisperBack.utils
  56.  
  57. #pylint: disable=R0902
  58. class WhisperBackUI(object):
  59.     """
  60.     This class provides a window containing the GTK+ user interface.
  61.  
  62.     """
  63.  
  64.     def __init__(self):
  65.         """Constructor of the class, which creates the main window
  66.  
  67.         This is where the main window will be created and filled with the 
  68.         widgets we want.
  69.         """
  70.  
  71.         builder = gtk.Builder()
  72.         builder.set_translation_domain('whisperback')
  73.         builder.add_from_file(os.path.join(whisperBack.utils.get_datadir(),
  74.                                           "whisperback.ui"))
  75.         builder.connect_signals(self)
  76.  
  77.         self.main_window = builder.get_object("windowMain")
  78.         self.vbox_top_left = builder.get_object("vboxTopLeft")
  79.         self.progression_dialog = builder.get_object("dialogProgression")
  80.         self.progression_main_text = builder.get_object("progressLabelMain")
  81.         self.progression_progressbar = builder.get_object("progressProgressbar")
  82.         self.progression_secondary_text = \
  83.             builder.get_object("progressLabelSecondary")
  84.         self.progression_close = builder.get_object("progressButtonClose")
  85.         self.gpg_dialog = builder.get_object("dialogGpgkeyblock")
  86.         self.gpg_keyblock = builder.get_object("textviewGpgKeyblock")
  87.         self.gpg_ok = builder.get_object("buttonGpgOk")
  88.         self.gpg_cancel = builder.get_object("buttonGpgClose")
  89.         self.subject = builder.get_object("entrySubject")
  90.         self.message = builder.get_object("textviewMessage")
  91.         self.contact_email = builder.get_object("entryMail")
  92.         self.contact_gpg_keyblock = builder.get_object("buttonGPGKeyBlock")
  93.         self.prepended_details = \
  94.             builder.get_object("textviewPrependedInfo")
  95.         self.include_prepended_details = \
  96.             builder.get_object("checkbuttonIncludePrependedInfo")
  97.         self.appended_details = builder.get_object("textviewAppendedInfo")
  98.         self.include_appended_details = \
  99.             builder.get_object("checkbuttonIncludeAppendedInfo")
  100.         self.help_container = builder.get_object("scrolledwindowHelp")
  101.         self.send_button = builder.get_object("buttonSend")
  102.  
  103.         try:
  104.             self.main_window.set_icon_from_file(os.path.join(
  105.                 whisperBack.utils.get_pixmapdir(), "whisperback.svg"))
  106.         except gobject.GError, e:
  107.             print e
  108.  
  109.         underline = lambda str: str + "\n" + len(str) * '-'
  110.  
  111.         #pylint: disable=C0301
  112.         self.message.get_buffer().insert_with_tags(
  113.             self.message.get_buffer().get_start_iter(),
  114.                 underline ( _("Name of the affected software") ) + "\n"*4 +
  115.                 underline ( _("Exact steps to reproduce the problem") ) + "\n"*4 +
  116.                 underline ( _("Actual result / the problem") ) + "\n"*4 +
  117.                 underline ( _("Desired result") ) + "\n"*4,
  118.             self.message.get_buffer().create_tag(family="Monospace"))
  119.  
  120.         #pylint: disable=E1101
  121.         self.htmlhelp = webkit.WebView()
  122.  
  123.         # Load only local ressources in the embedded webkit
  124.         # Loading untrusted ressources in such an unprotected browser
  125.         # wouldn't be safe
  126.         #pylint: disable=C0111,R0913
  127.         def cb_request_starting(web_view, web_frame, web_ressource, request,
  128.                                 response, user_data=None):
  129.             if not request.get_uri().startswith("file://"):
  130.                 webbrowser.open_new(request.get_uri())
  131.                 request.set_uri(web_frame.get_uri())
  132.         self.htmlhelp.connect("resource-request-starting", cb_request_starting)
  133.         self.htmlhelp.get_settings().set_property("user-stylesheet-uri", "file://" +
  134.             whisperBack.utils.get_datadir() + "/style.css")
  135.  
  136.         # set the two main window areas to be each half of the window
  137.         # on big screens
  138.         if self.main_window.get_screen().get_width() > 800:
  139.             self.vbox_top_left.set_size_request(400, -1)
  140.             self.help_container.set_size_request(400, -1)
  141.  
  142.         self.main_window.maximize()
  143.  
  144.         self.main_window.show()
  145.  
  146.         # Launches the backend
  147.         try:
  148.             self.backend = whisperBack.whisperback.WhisperBack()
  149.         except whisperBack.exceptions.MisconfigurationException, e:
  150.             self.show_exception_dialog(
  151.                 _("Unable to load a valid configuration."), e,
  152.                 self.cb_close_application)
  153.             return
  154.  
  155.         # Loads help
  156.         self.load_htmlhelp()
  157.         self.help_container.add_child(builder, self.htmlhelp, None)
  158.         self.htmlhelp.show()
  159.  
  160.         # Shows the debugging details
  161.         self.prepended_details.get_buffer().set_text(
  162.             self.backend.prepended_data.rstrip())
  163.         self.appended_details.get_buffer().set_text(
  164.             self.backend.appended_data.rstrip())
  165.  
  166.     # CALLBACKS
  167.     def cb_close_application(self, widget, data=None):
  168.         """Callback function for the main window's close event
  169.  
  170.         """
  171.         self.close_application()
  172.         return False
  173.  
  174.     def load_htmlhelp(self):
  175.         """Loads help into the help browser
  176.  
  177.         """
  178.         self.htmlhelp.load_string(self.backend.html_help,
  179.             "text/html",
  180.             "UTF-8",
  181.             "file:///")
  182.  
  183.     def cb_help_prev(self, widget, data=None):
  184.         """Callback function to go back in help browser
  185.  
  186.         """
  187.         if self.htmlhelp.can_go_back():
  188.             self.htmlhelp.go_back()
  189.         else:
  190.             self.load_htmlhelp()
  191.  
  192.     def cb_show_help(self, widget, data=None):
  193.         """Callback function display help main page in help browser
  194.  
  195.         """
  196.         self.load_htmlhelp()
  197.  
  198.     def cb_show_about(self, widget, data=None):
  199.         """Callback function to show the "about" dialog
  200.  
  201.         """
  202.         self.show_about_dialog()
  203.         return False
  204.  
  205.     def cb_enter_gpgkeyblock(self, widget, data=None):
  206.         """Callback function to show the gpg publick key block input dialog
  207.  
  208.         """
  209.         self.show_gpg_dialog()
  210.         return False
  211.  
  212.     def cb_send_message(self, widget, data=None):
  213.         """Callback function to actually send the message
  214.  
  215.         """
  216.  
  217.         self.progression_dialog.set_title(_("Sending mail..."))
  218.         self.progression_main_text.set_text(_("Sending mail"))
  219.         #pylint: disable=C0301
  220.         self.progression_secondary_text.set_text(_("This could take a while..."))
  221.         self.progression_dialog.set_transient_for(self.main_window)
  222.         self.progression_dialog.show()
  223.         self.main_window.set_sensitive(False)
  224.  
  225.         self.backend.subject = self.subject.get_text()
  226.         self.backend.message = self.message.get_buffer().get_text(
  227.                                self.message.get_buffer().get_start_iter(),
  228.                                self.message.get_buffer().get_end_iter())
  229.         if self.contact_email.get_text():
  230.             try:
  231.                 self.backend.contact_email = self.contact_email.get_text()
  232.             except ValueError, e:
  233.                 self.show_exception_dialog(
  234.                     _("The contact email adress doesn't seem valid."), e)
  235.                 self.progression_dialog.hide()
  236.                 return
  237.  
  238.         if not self.include_prepended_details.get_active():
  239.             self.backend.prepended_data = ""
  240.         if not self.include_appended_details.get_active():
  241.             self.backend.appended_data = ""
  242.  
  243.         #pylint: disable=C0111
  244.         def cb_update_progress():
  245.             self.progression_progressbar.pulse()
  246.  
  247.         #pylint: disable=C0111
  248.         def cb_finished_progress(e):
  249.             if isinstance(e, Exception):
  250.                 if isinstance(e, smtplib.SMTPException):
  251.                     exception_string = _("Unable to send the mail: SMTP error.")
  252.                 elif isinstance(e, socket.error):
  253.                     exception_string = _("Unable to connect to the server.")
  254.                 else:
  255.                     exception_string = _("Unable to create or to send the mail.")
  256.  
  257.                 if self.backend.send_attempts <= 1:
  258.                     self.show_exception_dialog(exception_string + _("\n\n\
  259. The bug report could not be sent, likely due to network problems. \
  260. Please try to reconnect to the network and click send again.\n\
  261. \n\
  262. If it does not work, you will be offered to save the bug report."), e)
  263.                 else:
  264.                     self.show_exception_dialog_with_save(exception_string, e)
  265.                 self.progression_dialog.hide()
  266.             else:
  267.                 self.main_window.set_sensitive(False)
  268.                 self.progression_close.set_sensitive(True)
  269.                 self.progression_progressbar.set_fraction(1.0)
  270.                 self.progression_main_text.set_text(
  271.                     _("Your message has been sent."))
  272.                 self.progression_secondary_text.set_text("")
  273.  
  274.         try:
  275.             self.backend.send(cb_update_progress, cb_finished_progress)
  276.         except whisperBack.exceptions.EncryptionException, e:
  277.             self.show_exception_dialog(
  278.                 _("An error occured during encryption."), e)
  279.             self.progression_dialog.hide()
  280.  
  281.         return False
  282.  
  283.     def show_exception_dialog_with_save(self, message, exception):
  284.         """Shows a dialog reporting an exception and prompting the user to
  285.         save the debugging data as a file
  286.  
  287.         @param message          A string explaining the exception
  288.         @param exception        The exception
  289.         @param close_callback   An alternative callback to use on closing
  290.         @param buttons          Buttons to display
  291.         """
  292.         #pylint: disable=C0111
  293.         def cb_save_response(widget, event, data=None):
  294.             if event == gtk.RESPONSE_ACCEPT:
  295.                 try:
  296.                     self.backend.save(widget.get_filename())
  297.                 except IOError, e:
  298.                     self.show_exception_dialog(_("Unable to save %s.")
  299.                                                % widget.get_filename(), e)
  300.             widget.hide()
  301.             self.main_window.set_sensitive(True)
  302.  
  303.         #pylint: disable=C0111
  304.         def cb_response(widget, event, data=None):
  305.             widget.hide()
  306.             if event == gtk.RESPONSE_YES:
  307.                 save_dialog = gtk.FileChooserDialog(title=None,
  308.                               parent=self.main_window,
  309.                               action=gtk.FILE_CHOOSER_ACTION_SAVE,
  310.                               buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
  311.                                        gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT))
  312.  
  313.  
  314.                 save_dialog.set_local_only(True)
  315.                 save_dialog.connect("response", cb_save_response)
  316.                 save_dialog.show()
  317.  
  318.             else:
  319.                 self.main_window.set_sensitive(True)
  320.  
  321.         #XXX: fix string
  322.         suggestion = _("The bug report could not be sent, likely \
  323. due to network problems.\n\
  324. \n\
  325. As a work-around you can save the bug report as a file on a USB drive and try \
  326. to send it to us at %s from your email account using another system. \
  327. Note that your bug report will not be \
  328. anonymous when doing so unless you take further steps yourself (e.g. using \
  329. Tor with a throw-away email account).\n\
  330. \n\
  331. Do you want to save the bug report to a file?" % self.backend.to_address)
  332.         self.show_exception_dialog(message + "\n\n" + suggestion, exception,
  333.                                    parent=self.progression_dialog,
  334.                                    close_callback=cb_response,
  335.                                    buttons=gtk.BUTTONS_YES_NO)
  336.  
  337.     def show_exception_dialog(self, message, exception,
  338.                             close_callback=None, parent=None,
  339.                             buttons=gtk.BUTTONS_CLOSE):
  340.         """Shows a dialog reporting an exception
  341.  
  342.         @param message          A string explaining the exception
  343.         @param exception        The exception
  344.         @param close_callback   An alternative callback to use on closing
  345.         @param buttons          Buttons to display
  346.         """
  347.  
  348.         if not close_callback:
  349.             close_callback = self.cb_close_exception_dialog
  350.  
  351.         if not parent:
  352.             parent = self.main_window
  353.  
  354.         if isinstance(exception.message, types.MethodType):
  355.             exception_message = exception.message()
  356.         else:
  357.             exception_message = str(exception)
  358.  
  359.         dialog = gtk.MessageDialog(parent=parent,
  360.                                    flags=gtk.DIALOG_MODAL,
  361.                                    type=gtk.MESSAGE_ERROR,
  362.                                    buttons=buttons,
  363.                                    message_format=message)
  364.         dialog.format_secondary_text(exception_message)
  365.         
  366.         dialog.connect("response", close_callback)
  367.         dialog.show()
  368.         print traceback.format_exc()
  369.  
  370.     def cb_close_exception_dialog(self, widget, data=None):
  371.         """Callback function for the exception dialog close event
  372.         
  373.         """
  374.         self.main_window.set_sensitive(True)
  375.         widget.hide()
  376.         return False
  377.  
  378.     def show_about_dialog(self):
  379.         """Shows an "about" dialog for the program
  380.         
  381.         """
  382.  
  383.         about_dialog = gtk.AboutDialog()
  384.         about_dialog.set_transient_for(self.main_window)
  385.         about_dialog.set_version(__version__)
  386.         about_dialog.set_name(_("WhisperBack"))
  387.         about_dialog.set_comments(_("Send feedback in an encrypted mail."))
  388.         about_dialog.set_license(__licence__)
  389.         about_dialog.set_copyright(
  390.             _("Copyright ┬⌐ 2009-2012 Tails developpers (tails@boum.org)"))
  391.         about_dialog.set_authors([_("Tails developers <tails@boum.org>")])
  392.         about_dialog.set_translator_credits(_("translator-credits"))
  393.         about_dialog.set_website("https://tails.boum.org/")
  394.         about_dialog.connect("response", gtk.Widget.hide_on_delete)
  395.         about_dialog.show()
  396.  
  397.     def show_gpg_dialog(self):
  398.         """Show a text entry dialog to let the user enter a GPG public key block
  399.  
  400.         """
  401.         if self.backend.contact_gpgkey:
  402.             #pylint: disable=C0301
  403.             self.gpg_keyblock.get_buffer().set_text(str(self.backend.contact_gpgkey))
  404.         else:
  405.             self.gpg_keyblock.get_buffer().set_text("")
  406.         self.gpg_dialog.show()
  407.  
  408.     def cb_gpg_close_ok(self, widget, data=None):
  409.         """Callback function for the gpg publick key entry close and apply event
  410.  
  411.         """
  412.         try:
  413.             #pylint: disable=C0301
  414.             self.backend.contact_gpgkey = self.gpg_keyblock.get_buffer().get_text(
  415.                 self.gpg_keyblock.get_buffer().get_start_iter(),
  416.                 self.gpg_keyblock.get_buffer().get_end_iter())
  417.         except ValueError, e:
  418.             self.show_exception_dialog(
  419.                 _("This doesn's seem to be a valid URL or OpenPGP key."),
  420.                 e, parent=self.gpg_dialog)
  421.             return
  422.         self.gpg_dialog.hide()
  423.  
  424.     def cb_gpg_close_cancel(self, widget, data=None):
  425.         """Callback function for the gpg pyblick key entry cancel event
  426.  
  427.         """
  428.         self.gpg_dialog.hide()
  429.  
  430.     #pylint: disable=R0201
  431.     def close_application(self):
  432.         """
  433.         Closes the application
  434.  
  435.         """
  436.         gtk.main_quit()
  437.